//////////////////////////////////////////////////////////////////////////
// by George Ulyanov
// (c) 2010
//////////////////////////////////////////////////////////////////////////

// Loggr::Loggr
// Simple lightweight cross-platform logger.

#include "Config.h"

#ifndef LOGGR_LOGGR_H_
#   include "Loggr.h"
#endif

#include "Tools/Thread.h"
#include "Tools/Guard.h"
#include "Tools/TimeCounter.h"
#include "Tools/Filer.h"

#include <memory>
#include <stdexcept>
#include <time.h>
#include <stdarg.h>

namespace Loggr
{
    struct LoggrData
    {
        std::auto_ptr< Tools::Filer >   m_filer;
        Tools::Guard                    m_dataGuard;
        Tools::Guard                    m_flusherGuard;
        Tools::Thread                   m_flusherThread;
        Tools::TimeCounter              m_timer;
    };

    Loggr::Loggr():
        m_maxBytesPerAdd(1024),
        m_maxBuffersSize(4096),
        m_buffer1(0),
        m_buffer2(0),
        m_currBuffer(0),
        m_offset(0),
        m_needFlush(false),
        m_stopFlusher(false)
    {
        m_data = new LoggrData();
        if (!m_data)
        {
            throw std::runtime_error("Couldn't initialize Loggr::LoggrData.");
        }

        m_buffer1 = new char[m_maxBuffersSize];
        if (!m_buffer1)
        {
            throw std::runtime_error("Loggr: couldn't create output buffer #1.");
        }
        memset(m_buffer1, 0, m_maxBuffersSize);
        m_buffer2 = new char[m_maxBuffersSize];
        if (!m_buffer2)
        {
            delete [] m_buffer1;
            throw std::runtime_error("Loggr: couldn't create output buffer #2.");
        }
        memset(m_buffer2, 0, m_maxBuffersSize);
        m_currBuffer = m_buffer1;

        try
        {
            m_data->m_filer.reset(new Tools::Filer());
        }
        catch (...)
        {
            throw;
        }
        if (!m_data->m_filer.get())
        {
            delete [] m_buffer1;
            delete [] m_buffer2;
            throw std::runtime_error("Loggr: couldn't open a file.");
        }

        m_data->m_flusherThread.Start(Loggr::BuffersXchange, this);

        m_data->m_timer.Reset();
    }
    // Loggr::Loggr

    Loggr::~Loggr() throw()
    {
        m_data->m_flusherGuard.Lock();
        m_needFlush = true;
        m_data->m_flusherGuard.Unlock();
        while (m_needFlush)    Tools::Thread::Waiter(1);

        m_data->m_flusherGuard.Lock();
        if (m_currBuffer == m_buffer1)
        {
            m_currBuffer = m_buffer2;
        }
        else
        {
            m_currBuffer = m_buffer1;
        }
        m_needFlush = true;
        m_data->m_flusherGuard.Unlock();
        while (m_needFlush)    Tools::Thread::Waiter(1);

        m_stopFlusher = true;
        m_data->m_flusherThread.Wait();

        delete [] m_buffer1;
        delete [] m_buffer2;

        delete m_data;
    }
    // Loggr::~Loggr

    void Loggr::ForceXchange()
    {
        m_data->m_dataGuard.Lock();
        m_data->m_flusherGuard.Lock();

        m_data->m_filer->Write(m_currBuffer, strlen(m_currBuffer));
        memset(m_currBuffer, 0, m_maxBuffersSize);
        m_offset = 0;

        m_data->m_flusherGuard.Unlock();
        m_data->m_dataGuard.Unlock();
    }
    // Loggr::ForceXchange

    void Loggr::Write(const char* pStr, ...)
    {
		va_list argList;
		va_start(argList, pStr);

        m_data->m_dataGuard.Lock();
        char* prepart = new char[m_maxBytesPerAdd];
        char* part = new char[m_maxBytesPerAdd];
        if (prepart && part)
        {
            static size_t lineCounter = 1;

            vsprintf_s(prepart, m_maxBytesPerAdd, pStr, argList);
            time_t timer;
            time(&timer);
            tm* t = localtime(&timer);
            sprintf_s(part, m_maxBytesPerAdd,
                "%6d: (%d-%02d-%02d, %02d:%02d:%02d): %s\r\n",
                lineCounter++,
                1900 + t->tm_year, t->tm_mon + 1,  t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
                prepart);
            
            if ((m_offset + strlen(part)) >= (m_maxBuffersSize - 2))
            {
                while (m_needFlush)  Tools::Thread::Waiter(1);

                m_data->m_flusherGuard.Lock();
                m_offset = 0;
                if (m_currBuffer == m_buffer1)
                {
                    m_currBuffer = m_buffer2;
                }
                else
                {
                    m_currBuffer = m_buffer1;
                }
                m_needFlush = true;
                m_data->m_flusherGuard.Unlock();

                Tools::Thread::Waiter(1);
            }
            strcpy(m_currBuffer + m_offset, part);
            m_offset += strlen(part);

            delete [] part;
            delete [] prepart;
        }
        m_data->m_timer.Reset();
        m_data->m_dataGuard.Unlock();

		va_end(argList);
    }
    // Loggr::Write

    int Loggr::BuffersXchange(void* p)
    {
        if (p)
        {
            Loggr* loggr = static_cast< Loggr* >(p);
            if (loggr)
            {
                while (loggr && !(loggr->m_stopFlusher))
                {
                    loggr->m_data->m_flusherGuard.Lock();
                    if (loggr->m_needFlush)
                    {
                        loggr->m_needFlush = false;
                        if (loggr->m_currBuffer == loggr->m_buffer1)
                        {
                            loggr->m_data->m_filer->Write(loggr->m_buffer2, strlen(loggr->m_buffer2));
                            memset(loggr->m_buffer2, 0, loggr->m_maxBuffersSize);
                        }
                        else
                        {
                            loggr->m_data->m_filer->Write(loggr->m_buffer1, strlen(loggr->m_buffer1));
                            memset(loggr->m_buffer1, 0, loggr->m_maxBuffersSize);
                        }
                    }
                    loggr->m_data->m_flusherGuard.Unlock();
                    Tools::Thread::Waiter(1);
                }
            }
        }
        return 1;
    }
    // Loggr::BuffersXchange
}
// namespace Loggr
